#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/atomic.h>

struct epoll_demo_data
{
    atomic_t unread;
    wait_queue_head_t wait_queue;
};

static struct cdev epoll_demo_cdev;
static struct class* epoll_demo_cls;
static struct device* epoll_demo_device;
static dev_t epoll_demo_devno;

static const char* class_name = "epoll_demo";
static const char* module_name = "epoll_demo";
static const char* device_name = "epoll_demo";

static int epoll_demo_open(struct inode* inode, struct file* filp)
{
    struct epoll_demo_data* data = kzalloc(sizeof(struct epoll_demo_data), GFP_KERNEL);
    filp->private_data = data;

    init_waitqueue_head(&data->wait_queue);
    atomic_set(&data->unread, 0);

    return 0;
}

static int epoll_demo_release(struct inode* inode, struct file* filp)
{
    kfree(filp->private_data);
    return 0;
}


static ssize_t epoll_demo_read(struct file* filp, char __user* buffer, size_t count, loff_t* f_pos)
{
    struct epoll_demo_data* data = filp->private_data;
    int unread = atomic_read(&data->unread);
    if (unread != 0)
    {
        atomic_dec(&data->unread);
        return 4;
    }
    else
    {
        return 0;
    }
}

static ssize_t epoll_demo_write(struct file* filp, const char __user* buf, size_t size, loff_t* ppos)
{
    struct epoll_demo_data* data = filp->private_data;

    atomic_inc(&data->unread);
    wake_up_interruptible(&data->wait_queue);    //wake up thread waiting to read

    return size;     //注意，不能return 0。否则echo "xxxx" >/dev/epoll_demo会一直尝试执行
}

static unsigned int epoll_demo_poll(struct file* filp, poll_table* wait)
{
    unsigned int mask = 0;
    struct epoll_demo_data* data = filp->private_data;

    poll_wait(filp, &data->wait_queue, wait);

    if (atomic_read(&data->unread) != 0)
    {
        mask |= POLLIN;
    }

    return mask;
}

static const struct file_operations epoll_demo_fops =
{
    .owner = THIS_MODULE,
    .write = epoll_demo_write,
    .read = epoll_demo_read,
    .open = epoll_demo_open,
    .release = epoll_demo_release,
    .poll = epoll_demo_poll,
};

static int __init epoll_demo_init(void)
{
    int ret;
    ret = alloc_chrdev_region(&epoll_demo_devno, 0, 1, module_name);
    if (0 != ret)
    {
        return ret;
    }

    cdev_init(&epoll_demo_cdev, &epoll_demo_fops);
    epoll_demo_cdev.owner = THIS_MODULE;
    ret = cdev_add(&epoll_demo_cdev, epoll_demo_devno, 1);
    if (ret != 0)
    {
        goto fail1;
    }

    epoll_demo_cls = class_create(THIS_MODULE, class_name);
    if (IS_ERR(epoll_demo_cls))
    {
        ret = PTR_ERR(epoll_demo_cls);
        goto fail2;
    }

    epoll_demo_device = device_create(epoll_demo_cls, NULL, epoll_demo_devno, NULL, device_name);
    if (IS_ERR(epoll_demo_device))
    {
        ret = PTR_ERR(epoll_demo_device);
        goto fail3;
    }
    return 0;

fail3:
    class_destroy(epoll_demo_cls);
fail2:
    cdev_del(&epoll_demo_cdev);
fail1:
    unregister_chrdev_region(epoll_demo_devno, 1);
    return ret;
}
module_init(epoll_demo_init);

static void __exit epoll_demo_exit(void)
{
    device_destroy(epoll_demo_cls, epoll_demo_devno);
    class_destroy(epoll_demo_cls);
    cdev_del(&epoll_demo_cdev);
    unregister_chrdev_region(epoll_demo_devno, 1);
}
module_exit(epoll_demo_exit);

MODULE_LICENSE("GPL");
